#include "WNPlatform/inc/WNSurfaceManagerWindows.h"
#include "WNPlatform/inc/WNSurfaceWindows.h"
#include "WNMemory/inc/WNAllocation.h"
#include "WNMemory/inc/WNManipulation.h"
#include <vector>

using namespace WNPlatform;
const LPCTSTR sClass = "WNWindowClass";
WNSurfaceManager::WNSurfaceManagerError WNSurfaceManagerWindows::Initialize() {
    WNDCLASSEX wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WNWindowProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = GetModuleHandle(NULL);
    wcex.hIcon          = NULL;
    wcex.hCursor        = NULL;
    wcex.hbrBackground  = NULL;
    wcex.lpszMenuName   = NULL;
    wcex.lpszClassName  = sClass;
    wcex.hIconSm        = NULL;

    if (!RegisterClassEx(&wcex))
    {
        WNMemory::WNMemSet(&wcex, 0, sizeof(WNDCLASSEX));
        if(GetClassInfoEx(GetModuleHandle(NULL), sClass, &wcex) == 0){
            return(WNSurfaceManager::eSMENInitializationFailure);
        }
    }
    
    return(WNSurfaceManager::eWNOK);
}

WNSurfaceManagerWindows::WNSurfaceManagerWindows() :
    mPendingHwnd(0){   
}

WNSurfaceManager::WNSurfaceManagerError WNSurfaceManagerWindows::Release() {
    for(WN_SIZE_T i = 0; i < mMessagePumps.size(); ++i) {
        SendNotifyMessage(mMessagePumps[i]->mWindow->GetNativeWindowHandle(), WM_USER, 0, 0);
    }
    for(WN_SIZE_T i = 0; i < mMessagePumps.size(); ++i) {
        mMessagePumps[i]->mThread->Join();
        WN_DELETE(mMessagePumps[i]);
    }
    mMessagePumps.clear();
    return(eWNOK);
}

WNSurfaceManager::WNSurfaceManagerError WNSurfaceManagerWindows::CreateSurface(WN_UINT32 _x, WN_UINT32 _y, WN_UINT32 _width, WN_UINT32 _height, WNContainers::WNResourcePointer<WNSurface>& _surface) {
    
    WNContainers::WNResourcePointer<WNPlatform::WNSurfaceWindows> ptr = WNContainers::WNAllocateResource1<WNPlatform::WNSurfaceWindows, WNPlatform::WNSurfaceManagerWindows&>(*this);
    ptr->Resize(_width, _height);
    ptr->Move(_x, _y);

    WindowThread* dat = WN_NEW(WindowThread(ptr));
    mWindowCreationLock.Lock();
    mPendingHwnd = 0;

    WNConcurrency::WNThread* thread = WN_NEW(WNConcurrency::WNThread(WNMessagePump, dat));
    dat->mThread = thread;

    if (!thread->IsValid()) {
        WN_DELETE(thread);

        return(WNSurfaceManager::eWNError);
    }

    mCreatedWindowLock.Lock();

    HWND wnd = mPendingHwnd;

    mWindowCreationLock.Unlock();

    if (wnd == 0) {
        thread->Join();

        WN_DELETE(thread);
        return(WNSurfaceManager::eWNError);
    }

    mMessagePumps.push_back(dat);
    _surface = ptr;
    return(eWNOK);
}

LRESULT CALLBACK WNSurfaceManagerWindows::WNWindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) {
    WindowThread* data = reinterpret_cast<WindowThread*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
    if(data) {
        data->mWindow->ProcessCallback(uMsg, lParam, wParam);
    }
    switch(uMsg) {
        case WM_USER:
            if(wParam == lParam && wParam == 0) {
                if(data) {
                    data->mExit = true;
                }
                return(-1);
            }
        case WM_DESTROY:
            {
                if(data) {
                    data->mExit = true;
                    return(DefWindowProc(hwnd, uMsg, wParam, lParam));
                }
            }
        default: {
            return(DefWindowProc(hwnd, uMsg, wParam, lParam));
            break;
        }
    }
    return(0);
}


WN_BOOL WNSurfaceManagerWindows::WNMessagePump(WindowThread* _data) {
    //Create the window
    WNSurfaceManagerWindows& manager = _data->mWindow->mSurfaceManager;
    RECT r;
    r.left = _data->mWindow->GetX();
    r.right = _data->mWindow->GetX() + _data->mWindow->GetWidth();
    r.top = _data->mWindow->GetY();
    r.bottom = _data->mWindow->GetY() + _data->mWindow->GetHeight();


    AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, false);

    manager.mPendingHwnd = CreateWindowEx( 0, sClass, "", WS_OVERLAPPEDWINDOW, _data->mWindow->GetX(), _data->mWindow->GetY(), r.right - r.left, r.bottom - r.top, 0, 0, GetModuleHandle(NULL), NULL); 
    HWND wnd = manager.mPendingHwnd;

    if (manager.mPendingHwnd == 0){
        manager.mCreatedWindowLock.Unlock();
        return(WN_FALSE);
    }
    _data->mWindow->SetNativeWindowHandle(wnd);
    manager.mCreatedWindowLock.Unlock();
    SetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(_data));
    ShowWindow(wnd, SW_SHOW);
    MSG msg;
    while(1) {
        //UGG a little ugly.
        while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        if(_data->mExit) {
            break;
        }
        Sleep(1);
    }
    return(WN_TRUE);
}


WNPlatform::WNSurfaceManagerWindows::WindowThread::WindowThread(WNContainers::WNResourcePointer<WNPlatform::WNSurfaceWindows>(_wnd)) :
    mWindow(_wnd),
    mExit(false){
}
